package cn.aws360.boot.system.biz.service.impl;

import cn.aws360.boot.base.api.constant.enums.DelFlagEnum;
import cn.aws360.boot.base.api.exception.ServiceException;
import cn.aws360.boot.data.api.constant.DataConstant;
import cn.aws360.boot.data.api.factory.PageFactory;
import cn.aws360.boot.data.api.pojo.request.PageQuery;
import cn.aws360.boot.data.api.pojo.response.PageResult;
import cn.aws360.boot.data.api.utils.PojoUtils;
import cn.aws360.boot.system.api.constant.SysConstant;
import cn.aws360.boot.system.api.exception.SysExceptionEnum;
import cn.aws360.boot.system.api.pojo.request.SysDeptQuery;
import cn.aws360.boot.system.api.pojo.request.SysDeptWrite;
import cn.aws360.boot.system.api.pojo.response.SysDeptModel;
import cn.aws360.boot.system.biz.domain.SysDept;
import cn.aws360.boot.system.biz.domain.SysUser;
import cn.aws360.boot.system.biz.helper.TreeHelper;
import cn.aws360.boot.system.biz.mapper.SysDeptMapper;
import cn.aws360.boot.system.biz.mapper.SysUserMapper;
import cn.aws360.boot.system.biz.service.SysDeptService;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author junnan
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> implements SysDeptService {

    private final SysUserMapper userMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_DEPT_CACHE, allEntries = true)
    public Boolean add(SysDeptWrite sysDeptWrite) {
        SysDept sysDept = BeanUtil.copyProperties(sysDeptWrite, SysDept.class, DataConstant.ID);
        SysDept parSysDept = getSysDeptById(sysDept.getPid());
        sysDept.setAncestors(parSysDept.getAncestors() + StrUtil.COMMA + sysDept.getPid());
        return this.save(sysDept);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_DEPT_CACHE, allEntries = true)
    public Boolean edit(SysDeptWrite sysDeptWrite) {
        Integer deptId = sysDeptWrite.getId();
        checkSysDontUpdate(deptId);
        SysDept sysDept = this.getSysDeptById(deptId);
        BeanUtil.copyProperties(sysDeptWrite, sysDept);
        checkSubDontUpdate(sysDept);
        String oldAncestors = sysDept.getAncestors();
        SysDept parSysDept = getSysDeptById(sysDept.getPid());
        sysDept.setAncestors(parSysDept.getAncestors() + StrUtil.COMMA + sysDept.getPid());
        boolean flag = this.updateById(sysDept);
        if (flag) {
            return updateSubDeptAncestors(sysDept, oldAncestors);
        }
        return false;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_DEPT_CACHE, allEntries = true)
    public Boolean remove(String ids) {
        List<Integer> deptIdList = Convert.toList(Integer.class, ids.split(StrUtil.COMMA));
        deptIdList.forEach(this::checkSysDontDelete);
        List<SysDept> sysDeptList = this.listByIds(deptIdList);
        for (SysDept sysDept : sysDeptList) {
            checkHasItemDontDelete(sysDept);
            checkHasUserDontDelete(sysDept);
        }
        return this.removeByIds(deptIdList);
    }

    @Override
    public SysDeptModel detail(Integer id) {
        SysDept sysDept = this.getSysDeptById(id);
        return BeanUtil.copyProperties(sysDept, SysDeptModel.class);
    }

    @Override
    public List<SysDeptModel> findList(SysDeptQuery sysDeptQuery) {
        LambdaQueryWrapper<SysDept> queryWrapper = createQueryWrapper(sysDeptQuery);
        List<SysDept> sysDeptList = this.list(queryWrapper);
        return PojoUtils.copyToList(sysDeptList, SysDeptModel.class);
    }

    @Override
    public PageResult<SysDeptModel> findPage(SysDeptQuery sysDeptQuery, PageQuery pageQuery) {
        LambdaQueryWrapper<SysDept> queryWrapper = createQueryWrapper(sysDeptQuery);
        PageResult<SysDept> sysDeptPage = PageFactory.getPageResult(this, pageQuery, queryWrapper);
        return PojoUtils.copyToPage(sysDeptPage, SysDeptModel.class);
    }

    @Override
    public List<Tree<Integer>> findTree(SysDeptQuery sysDeptQuery) {
        LambdaQueryWrapper<SysDept> queryWrapper = createQueryWrapper(sysDeptQuery);
        List<SysDept> sysDeptList = this.list(queryWrapper);

        // TODO 部门数据权限
        List<TreeNode<Integer>> deptNodeList = TreeHelper.buildDeptTreeNodeList(sysDeptList);

        if (StrUtil.isNotBlank(sysDeptQuery.getName())) {
            return TreeHelper.buildTreeList(deptNodeList);
        }
        return TreeUtil.build(deptNodeList, 0);
    }

    @Override
    public List<Integer> findAllSubIdList(Integer deptId) {
        List<Integer> idList = new ArrayList<>(Collections.singletonList(deptId));
        List<SysDept> allSubList = findAllSubList(deptId);
        idList.addAll(allSubList.stream().map(SysDept::getId).collect(Collectors.toList()));
        return idList;
    }

    @Cacheable(value = SysConstant.MODULE_DEPT_CACHE, key = "#deptId")
    public List<SysDept> findAllSubList(Integer deptId) {
        LambdaQueryWrapper<SysDept> queryWrapper = Wrappers.<SysDept>lambdaQuery()
                .apply(deptId != null, StrUtil.format("FIND_IN_SET('{}', ancestors)", deptId))
                .orderByAsc(SysDept::getSort);
        return this.list(queryWrapper);
    }

    private SysDept getSysDeptById(Integer id) {
        SysDept sysDept = this.getById(id);
        checkNotExist(sysDept);
        return sysDept;
    }

    private LambdaQueryWrapper<SysDept> createQueryWrapper(SysDeptQuery deptQuery) {
        return Wrappers.<SysDept>lambdaQuery()
                .like(ObjectUtil.isNotEmpty(deptQuery.getName()), SysDept::getName, deptQuery.getName())
                .ne(ObjectUtil.isNotNull(deptQuery.getFilterAllSubId()), SysDept::getId, deptQuery.getFilterAllSubId())
                .orderByAsc(SysDept::getSort);
    }

    private void checkNotExist(SysDept sysDept) {
        if (ObjectUtil.isNull(sysDept) || DelFlagEnum.Y.getValue().equals(sysDept.getDelFlag())) {
            throw new ServiceException(SysExceptionEnum.DEPT_NOT_EXIST);
        }
    }

    private void checkSysDontUpdate(Integer deptId) {
        if (SysConstant.ROOT_ID.equals(deptId)) {
            throw new ServiceException(SysExceptionEnum.DEPT_DONT_UPDATE_FOR_ROOT);
        }
    }

    private void checkSubDontUpdate(SysDept sysDept) {
        Integer newPid = sysDept.getPid();
        List<Integer> allSubIdList = findAllSubIdList(sysDept.getId());
        if (allSubIdList.contains(newPid)) {
            throw new ServiceException(SysExceptionEnum.DEPT_DONT_UPDATE_FOR_TO_SUB);
        }
    }

    private void checkSysDontDelete(Integer deptId) {
        if (SysConstant.ROOT_ID.equals(deptId)) {
            throw new ServiceException(SysExceptionEnum.DEPT_DONT_DELETE_FOR_ROOT);
        }
    }

    private void checkHasItemDontDelete(SysDept sysDept) {
        Integer deptId = sysDept.getId();
        LambdaQueryWrapper<SysDept> queryWrapper = Wrappers.<SysDept>lambdaQuery().eq(SysDept::getPid, deptId);
        List<SysDept> sysDeptList = this.list(queryWrapper);
        if (CollUtil.isNotEmpty(sysDeptList)) {
            throw new ServiceException(SysExceptionEnum.DEPT_DONT_DELETE_FOR_HAS_ITEM, deptId);
        }
    }

    private void checkHasUserDontDelete(SysDept sysDept) {
        Integer deptId = sysDept.getId();
        LambdaQueryWrapper<SysUser> queryWrapper = Wrappers.<SysUser>lambdaQuery().eq(SysUser::getDeptId, deptId);
        List<SysUser> sysUserList = userMapper.selectList(queryWrapper);
        if (CollUtil.isNotEmpty(sysUserList)) {
            throw new ServiceException(SysExceptionEnum.DEPT_DONT_DELETE_FOR_HAS_USER, deptId);
        }
    }

    private boolean updateSubDeptAncestors(SysDept sysDept, String oldAncestors) {
        String newAncestors = sysDept.getAncestors();
        List<SysDept> allSubList = findAllSubList(sysDept.getId());
        for (SysDept subDept : allSubList) {
            String ancestors = subDept.getAncestors();
            subDept.setAncestors(ancestors.replaceFirst(oldAncestors, newAncestors));
        }
        return this.updateBatchById(allSubList);
    }

}
